package controller;

import java.io.IOException;
import java.lang.reflect.Type;
import java.util.Collections;
//import java.util.LinkedList;
import java.util.List;
import java.util.UUID;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.google.gson.Gson;
import com.google.gson.JsonSyntaxException;
import com.google.gson.reflect.TypeToken;

import common.Enums.ComponentType;
import common.Enums.DAOType;
import common.Enums.MethodType;
import common.Enums.ReliabilityType;
import common.Enums.SourceType;
import common.Exceptions.DAOException;
import common.Exceptions.FormulaException;
import common.Utils.CommonUtils;
import common.Utils.LinkedSet;
import common.Utils.StringUtils;

import model.*;
//import model.GsonMapping.CriterionWithCase;
//import model.GsonMapping.GsonCalculationMethod;
import model.GsonMapping.ParametersWithLetters;
import model.GsonMapping.SaveMethodData;
import model.GsonMapping.UpdatedResult;
import DAO.DAOFactory;
import DAO.MetAppDAO;
import DAO.SessionDAO;
import MySQLConnection.MySqlDAOFactory;

/**
 * Servlet implementation class MethodServlet
 */
public class MethodServlet extends BaseServlet 
{
	private static final long serialVersionUID = 1L;
	private final String METHOD_PAGE = "/methodDefinition.jsp";
	
	protected void loadData(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
	{
		try
		{
			BusinessCase bc = SessionDAO.getInstance().getCachedBC();
			
			if(bc != null)
			{
				request.setAttribute("BC", bc);
				
				LinkedSet<Criterion> criteria = SessionDAO.getInstance().getTopLevelCriteria();
				
				if(criteria != null)
					request.setAttribute("Criteria", criteria);
					
				request.setAttribute("SessionDAO", SessionDAO.getInstance());
			}
			
			request.setAttribute("reliabilityTypes", ReliabilityType.toStringArray());
			request.getRequestDispatcher(METHOD_PAGE).forward(request, response);
			System.out.println("has been directed to: " + METHOD_PAGE);
		}
		catch(Exception e)
		{
			StackTraceElement ste = Thread.currentThread().getStackTrace()[1];
			String location = "Class: " + ste.getClassName() + ", method: " + ste.getMethodName();
			SessionDAO.getInstance().showError(location, e.getMessage(), e);
			
			response.getWriter().write("ERROR");
		}
	}
	
	protected void loadRelatedCases(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
	{
		Gson gson = CommonUtils.createGsonInstance(response);
		
		try
		{
			response.getWriter().write(gson.toJson(SessionDAO.getInstance().getAllCachedCases()));
		}
		catch(Exception e)
		{
			StackTraceElement ste = Thread.currentThread().getStackTrace()[1];
			String location = "Class: " + ste.getClassName() + ", method: " + ste.getMethodName();
			SessionDAO.getInstance().showError(location, e.getMessage(), e);
			
			response.getWriter().write("ERROR");
		}
	}
	
	protected void getComponentByID(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
	{
		Gson gson = CommonUtils.createGsonInstance(response);
		
		try
		{
			String cmpType = request.getParameter("cmpType");
			UUID cmpID = UUID.fromString(request.getParameter("cmpID"));
			
			MethodApplication app = null;
			if(cmpType.equalsIgnoreCase("value"))
			{
				for(MethodApplication metApp : SessionDAO.getInstance().getMethodsApplication())
					if(metApp.getValue().getID().equals(cmpID))
							app = metApp;
			}
			else
			{
				for(MethodApplication metApp : SessionDAO.getInstance().getMethodsApplication())
					if(metApp.getCriterion().getID().equals(cmpID))
							app = metApp;
			}
			
			response.getWriter().write(gson.toJson(app));
		}
		catch (Exception e) 
		{
			String errorType = "ERROR";
			
			StackTraceElement ste = Thread.currentThread().getStackTrace()[1];
			String location = "Class: " + ste.getClassName() + ", method: " + ste.getMethodName();
			SessionDAO.getInstance().showError(location, e.getMessage(), e);
			
			response.getWriter().write(gson.toJson(errorType));
		}
	}
	
	protected void getFormula(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
	{
		Gson gson = CommonUtils.createGsonInstance(response);
		
		try
		{
			UUID valID = gson.fromJson(request.getParameter("valID"), UUID.class);
			CalculationMethod calMet = SessionDAO.getInstance().getCalculationMethodByValueID(valID);
			response.getWriter().write(gson.toJson(calMet));
		}
		catch (Exception e) 
		{
			StackTraceElement ste = Thread.currentThread().getStackTrace()[1];
			String location = "Class: " + ste.getClassName() + ", method: " + ste.getMethodName();
			SessionDAO.getInstance().showError(location, e.getMessage(), e);
			
			response.getWriter().write(gson.toJson("ERROR"));
		}
	}
	
	protected void validateFormula(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
	{
		Gson gson = CommonUtils.createGsonInstance(response);
		String error = "";
		
		try
		{
			String formula = request.getParameter("formula").toUpperCase();
			Type parametersDataType = new TypeToken<LinkedSet<String>>(){}.getType(); 
			LinkedSet<String> parameters = gson.fromJson(request.getParameter("parameters"), parametersDataType);
			
			if(!Formula.isFormulaValid(formula))
				error = "INVALID_SYNTAX";
			else
			{
				for(char c : formula.toCharArray())
					if(Character.isLetter(c) && !parameters.contains(String.valueOf(c)))
					{
						error = "MISSING_PARAMETER";
						break;
					}
			}			
			
			response.getWriter().write(gson.toJson(error));
		}
		catch (Exception e) 
		{
			StackTraceElement ste = Thread.currentThread().getStackTrace()[1];
			String location = "Class: " + ste.getClassName() + ", method: " + ste.getMethodName();
			SessionDAO.getInstance().showError(location, e.getMessage(), e);
			
			response.getWriter().write(gson.toJson("ERROR"));
		}
	}
	
	protected void saveFormula(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
	{
		Gson gson = CommonUtils.createGsonInstance(response);
		DAOFactory daoFactory = DAOFactory.getDAOFactory(DAOType.MYSQL);
		daoFactory.setAutoCommit(false);
		boolean lockOwner = MySqlDAOFactory.lockConnection.tryLock();
		
		try
		{
			String formula = request.getParameter("formula").toUpperCase();
			Type parametersDataType = new TypeToken<LinkedSet<ParametersWithLetters>>(){}.getType(); 
			LinkedSet<ParametersWithLetters> parameters = gson.fromJson(request.getParameter("parameters"), parametersDataType);
			UUID valID = gson.fromJson(request.getParameter("valID"), UUID.class);
			
			CalculationMethod calMet = SessionDAO.getInstance().getCalculationMethodByValueID(valID);
			
			if(calMet != null)
			{
				List<Case> relatedCases = SessionDAO.getInstance().getMethodRelatedCases(calMet.getID());
				LinkedSet<UpdatedResult> updatedValues = new LinkedSet<UpdatedResult>();
				LinkedSet<Criterion> forParameters = new LinkedSet<Criterion>();
				for(ParametersWithLetters param : parameters)
				{
					Criterion crt = SessionDAO.getInstance().getCriterionByID(param.getParamID());
					if(crt == null)
					{
						crt = new Criterion(param.getName(), param.getParamID());
						daoFactory.getCriterionDAO().insert(crt);
					}
					
					forParameters.add(crt);
					formula = formula.replace(param.getLetter(), param.getParamID().toString());
				}
				
				calMet.setFormulaString(formula, forParameters);
				daoFactory.getMethodDAO().update(calMet);
				
				for(Case caso : relatedCases)
				{
					Value val = calMet.refreshValue(caso);
					daoFactory.getValueDAO().update(val);
					updatedValues.add(new UpdatedResult(val.getStringResult(), val.getID()));
					updatedValues.addCollection(getPendingUpdates(val.getID(), val.getStringResult()));
				}
				
				daoFactory.commitChanges();
				response.getWriter().write(gson.toJson(updatedValues));
			}
			else
				response.getWriter().write(gson.toJson("CALCULATION_METHOD_NOT_FOUND"));
		}
		catch (Exception e) 
		{
			daoFactory.rollbackChanges();
			String errorType = "ERROR";
			
			if(e.getClass().equals(DAOException.class))
				errorType = "DATABASE_ERROR";
			
			StackTraceElement ste = Thread.currentThread().getStackTrace()[1];
			String location = "Class: " + ste.getClassName() + ", method: " + ste.getMethodName();
			SessionDAO.getInstance().showError(location, e.getMessage(), e);
			
			response.getWriter().write(gson.toJson(errorType));
		}
		finally
		{
			if(lockOwner)
			{
				MySqlDAOFactory.lockConnection.unlock();
				try {
					MySqlDAOFactory.getInstance().closeConnection();
				} catch (DAOException e) {
					e.printStackTrace();
				}
			}
		}
	}
	
	protected void getCalculationMethods(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
	{
		Gson gson = CommonUtils.createGsonInstance(response);
		
		try
		{
			UUID metID = gson.fromJson(request.getParameter("param"), UUID.class);
			List<CalculationMethod> calMethods = new LinkedSet<CalculationMethod>();
			
			for(Method met : DAOFactory.getDAOFactory(DAOType.MYSQL).getMethodDAO().getAll())
				if(met.getType() == MethodType.CALCULATION && !metID.equals(met.getID()))
					calMethods.add((CalculationMethod)met);
			
			Collections.sort(calMethods);
			response.getWriter().write(gson.toJson(calMethods));
		}
		catch (Exception e) 
		{
			String errorType = "ERROR";
			
			if(e.getClass().equals(DAOException.class))
				errorType = "DATABASE_ERROR";
			
			StackTraceElement ste = Thread.currentThread().getStackTrace()[1];
			String location = "Class: " + ste.getClassName() + ", method: " + ste.getMethodName();
			SessionDAO.getInstance().showError(location, e.getMessage(), e);
			
			response.getWriter().write(gson.toJson(errorType));
		}
	}
/*
	 * This method is responsible for getting all the related data to the calculation method chosen by the user. In order to to that, the following steps are executed:
	 *   1. Get the directly related criterion to the selected method.
	 *   2. Get the cases that are related to this criterion and include this information in the criterion to pass to the client.
	 *   3. Gets all the necessary method applications instances in order to map the values, criterion and cases in the client side
	 *   4. Returns the informations previously acquired. The method application is indexed by the case in order to facilitate the manipulation in the client side.   
	 * @param request
	 * @param response
	 * @throws ServletException
	 * @throws IOException
	protected void getCalculationMethod(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
	{
		Gson gson = CommonUtils.createGsonInstance(response);
		
		try
		{
			//TODO: Obtain the bcID of the BC that owns the method
			UUID metID = gson.fromJson(request.getParameter("param"), UUID.class);
			
			CalculationMethod calMet = (CalculationMethod) SessionDAO.getInstance().getMethodByID(metID);
			//TODO: Improve logic here -> Its assumes that the necessary method application are already loaded which may not be the case...if not find, submit query
			CriterionWithCase mainCrt = new CriterionWithCase(MethodApplication.getRelatedCriterion(metID, SessionDAO.getInstance().getMethodsApplication()).get(0));
			List<Case> relatedCases = SessionDAO.getInstance().getCriterionRelatedCases(mainCrt.getID());
			mainCrt.setCases(relatedCases);
			List<Criterion>	crtList = new LinkedList<Criterion>();
			crtList.addAll(calMet.getParameters());
			crtList.add(mainCrt);
			
			LinkedSet<MethodApplication> metApps = new LinkedSet<MethodApplication>();
			for(Case caso : relatedCases)
				for(Criterion crt : crtList)
				{
					//TODO: The problem here is to find only one method application. With the bcID we do a better filtering. But, the same criterion and case can appear more than once 
					//in the same bc in different calculation methods, for example, and with a different related method and value. How to find which one is the correct one?
					List<MethodApplication> metApp = SessionDAO.getInstance().getAllByCaseAndCriterionID(SessionDAO.getInstance().getCachedBC().getID(), crt.getID(), caso.getID());
					if(metApp.size() > 0)
						metApps.add(metApp.get(0));
				}
			
			GsonCalculationMethod gsonCalMet = new GsonCalculationMethod();
			gsonCalMet.setMainCrt(mainCrt);
			gsonCalMet.setMainMet(calMet);
			gsonCalMet.setMetApps(metApps);
			
			response.getWriter().write(gson.toJson(gsonCalMet));
		}
		catch (Exception e) 
		{
			String errorType = "ERROR";
			
			if(e.getClass().equals(DAOException.class))
				errorType = "DATABASE_ERROR";
			
			StackTraceElement ste = Thread.currentThread().getStackTrace()[1];
			String location = "Class: " + ste.getClassName() + ", method: " + ste.getMethodName();
			SessionDAO.getInstance().showError(location, e.getMessage(), e);
			
			response.getWriter().write(gson.toJson(errorType));
		}
	}*/
	
	protected void getCriteria(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
	{
		Gson gson = CommonUtils.createGsonInstance(response);
		
		try
		{
			UUID metID = gson.fromJson(request.getParameter("param"), UUID.class);
			List<Criterion> criteria = DAOFactory.getDAOFactory(DAOType.MYSQL).getCriterionDAO().getAll();
			List<Criterion> parameters = SessionDAO.getInstance().getMethodByID(metID).getParameters();
			
			for(Criterion param : parameters) //Filters the list so that no already used parameters is shown in the list...
				if(criteria.contains(param))
					criteria.remove(param);
			
			Collections.sort(criteria);
			response.getWriter().write(gson.toJson(criteria));
		}
		catch (Exception e) 
		{
			String errorType = "ERROR";
			
			if(e.getClass().equals(DAOException.class))
				errorType = "DATABASE_ERROR";
			
			StackTraceElement ste = Thread.currentThread().getStackTrace()[1];
			String location = "Class: " + ste.getClassName() + ", method: " + ste.getMethodName();
			SessionDAO.getInstance().showError(location, e.getMessage(), e);
			
			response.getWriter().write(gson.toJson(errorType));
		}
	}
	
	protected void getCases(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
	{
		Gson gson = CommonUtils.createGsonInstance(response);
		
		try
		{
			List<Case> cases = DAOFactory.getDAOFactory(DAOType.MYSQL).getCaseDAO().getAll();
			
			/* UUID metID = gson.fromJson(request.getParameter("param"), UUID.class);
			 * for(Case caso : SessionDAO.getInstance().getMethodRelatedCases(metID))
				if(cases.contains(caso))
					cases.remove(caso);*/
			
			Collections.sort(cases);
			response.getWriter().write(gson.toJson(cases));
		}
		catch (Exception e) 
		{
			String errorType = "ERROR";
			
			if(e.getClass().equals(DAOException.class))
				errorType = "DATABASE_ERROR";
			
			StackTraceElement ste = Thread.currentThread().getStackTrace()[1];
			String location = "Class: " + ste.getClassName() + ", method: " + ste.getMethodName();
			SessionDAO.getInstance().showError(location, e.getMessage(), e);
			
			response.getWriter().write(gson.toJson(errorType));
		}
	}
	
	protected void getValuesAndMethods(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
	{
		Gson gson = CommonUtils.createGsonInstance(response);
		
		try
		{
			//TODO: Include the direct values to be reused as well. The problem is that value don't have name.
			//Value currentValue = SessionDAO.getInstance().getValueByID(valueID);
			UUID valueID = gson.fromJson(request.getParameter("param"), UUID.class);
			List<Method> methods = DAOFactory.getDAOFactory(DAOType.MYSQL).getMethodDAO().getAll();
			
			Method relatedMethod = null;
			for(MethodApplication metApp :  SessionDAO.getInstance().getMethodsApplication())
				if(metApp.getValue().getID().equals(valueID))
				{
					relatedMethod = metApp.getMethod();
					break;
				}
			
			methods.remove(relatedMethod); 
			
			//Collections.sort(methods); -> Null pointer exception here, I don't know why..maybe the equals method are trying to compare something that is null... 
			response.getWriter().write(gson.toJson(methods));
		}
		catch (Exception e) 
		{
			String errorType = "ERROR";
			
			if(e.getClass().equals(DAOException.class))
				errorType = "DATABASE_ERROR";
			
			StackTraceElement ste = Thread.currentThread().getStackTrace()[1];
			String location = "Class: " + ste.getClassName() + ", method: " + ste.getMethodName();
			SessionDAO.getInstance().showError(location, e.getMessage(), e);
			
			response.getWriter().write(gson.toJson(errorType));
		}
	}
	
	private void createNewBC(List<MethodApplication> methodApplications)
	{
		BusinessCase bc = new BusinessCase();
		bc.setAuthor(System.getProperty("user.name"));
		
		for(MethodApplication metApp : methodApplications)
			SessionDAO.getInstance().addMethodApplication(metApp); //Sets all the used BC's components into cache.
		
		SessionDAO.getInstance().setCachedBC(bc); 
	}
	
	private void updateBCData(DAOFactory daoFactory, Criterion mainCrt, Criterion originalCrt, Method mainMethod, List<MethodApplication> methodApplications) throws DAOException
	{
		List<UUID> savedItems = new LinkedSet<UUID>();
		
		originalCrt.setDescription(mainCrt.getDescription());
		originalCrt.setName(mainCrt.getName());
		savedItems.add(originalCrt.getID());
		savedItems.add(mainMethod.getID());
		
		for(MethodApplication metApp : methodApplications)
		{
			/*TODO: Improve efficiency -> Implement a generic status attribute in order to identify if the item is new or loaded. 
			Via reflection, create a list only with the new ones and submit them in a for loop with the insert method. (2 list will be created, one only with new items and the other with the old) 
			For the other list, submit them with the update method. For this to work the client side must set the appropriate value for the status attribute.*/ 
			//List<UUID> insertedItems = new LinkedSet<UUID>();
			try
			{
				Criterion clientSideCriterion = metApp.getCriterion();
				if(!savedItems.contains(clientSideCriterion.getID()))
				{
					Criterion originalCriterion = SessionDAO.getInstance().getCriterionByID(clientSideCriterion.getID());
					if(originalCriterion != null)
					{
						CommonUtils.updateItem(originalCriterion, clientSideCriterion);
						daoFactory.getCriterionDAO().update(originalCriterion);
					}
					else
					{
						daoFactory.getCriterionDAO().insert(clientSideCriterion);
						mainMethod.addParamater(clientSideCriterion);
						daoFactory.getMethodDAO().update(mainMethod);
					}
					
					savedItems.add(clientSideCriterion.getID());
				}
				
				Method clientSideMethod = metApp.getMethod();
				if(!savedItems.contains(clientSideMethod.getID()))
				{
					Method originalMethod = SessionDAO.getInstance().getMethodByID(clientSideMethod.getID());
					if(originalMethod != null)
					{
						CommonUtils.updateItem(originalMethod, clientSideMethod);
						daoFactory.getMethodDAO().update(originalMethod);
					}
					else
					{
						if(StringUtils.containsLetter(metApp.getValue().getStringResult()))
							clientSideMethod.setType(MethodType.QUALITATIVE);
						else
							clientSideMethod.setType(MethodType.QUANTITATIVE);
						
						daoFactory.getMethodDAO().insert(clientSideMethod);
					}
					
					savedItems.add(clientSideMethod.getID());
				}
				
				Value clientSideValue = metApp.getValue();
				if(!savedItems.contains(clientSideValue.getID()))
				{
					Value originalValue = SessionDAO.getInstance().getValueByID(clientSideValue.getID());
					if(originalValue != null)
					{
						//TODO: The type should be setted in the client side when creating the object. But now it's not possible because there are only two possible types: 
						//Qualitative and quantitative, which the application doesn't discern. 
						CommonUtils.updateItem(originalValue, clientSideValue);
						
						if(!StringUtils.containsLetter(clientSideValue.getStringResult()) && originalValue.getType() == MethodType.QUALITATIVE)
							originalValue.setType(MethodType.QUANTITATIVE);
						
						daoFactory.getValueDAO().update(originalValue);
					}
					else if(clientSideValue.getStringResult() != null)
					{
						Value clientValue;
						if(StringUtils.containsLetter(metApp.getValue().getStringResult()))
							clientValue = new QualitativeValue(clientSideValue.getStringResult(), clientSideValue.getID());
						else
							clientValue = new QuantitativeValue(Double.parseDouble(clientSideValue.getStringResult()), clientSideValue.getID());
						
						clientValue.setUnit(clientSideValue.getUnit());
						clientValue.setReliability(ReliabilityType.GUESS);
						clientValue.setSourceType(SourceType.USER_INPUT);
						clientValue.setSource("");
						
						daoFactory.getValueDAO().insert(clientValue);
					}
					
					savedItems.add(clientSideValue.getID());
				}
				
				MethodApplication originalMetApp = SessionDAO.getInstance().getMethodApplicationByID(metApp.getID());
				if(originalMetApp != null)
				{
					originalMetApp.updateItem(metApp);
					daoFactory.getMetAppDAO().update(originalMetApp);
				}
				else
					daoFactory.getMetAppDAO().insert(SessionDAO.getInstance().getCachedBC().getID(), metApp);
			}
			catch(DAOException e)
			{
				throw e;
			}
		}
	}
	
	protected void saveMethodData(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
	{
		Gson gson = CommonUtils.createGsonInstance(response);
		DAOFactory daoFactory = DAOFactory.getDAOFactory(DAOType.MYSQL);
		daoFactory.setAutoCommit(false);
		boolean lockOwner = MySqlDAOFactory.lockConnection.tryLock(); 
		
		try
		{
			Type jspMethodDataType = new TypeToken<LinkedSet<SaveMethodData>>(){}.getType(); 
			LinkedSet<SaveMethodData> jspMethodPageData = gson.fromJson(request.getParameter("saveData"), jspMethodDataType);
			for(SaveMethodData metData : jspMethodPageData)
			{
				Method mainMethod = metData.getMainMethod();
				Criterion derMeasure = metData.getDerivedMeasure();
				LinkedSet<MethodApplication> methodApplications = metData.getMethodApplications();
				LinkedSet<Case> cases = metData.getCases();
				
				char currentCase = 'A';
				for(Case mainCase : cases) //Cases currently in the JSP page
				{
					if(mainCase.getName().trim().equals(""))
					{
						mainCase.setName("Case " + currentCase); //Sets default names for cases without names...
						currentCase++;
					}
					
					if(SessionDAO.getInstance().getCaseByID(mainCase.getID()) != null)
					{
						Case originalCase = SessionDAO.getInstance().getCaseByID(mainCase.getID());
						CommonUtils.updateItem(originalCase, mainCase);
						daoFactory.getCaseDAO().update(originalCase);
					}
					else
						daoFactory.getCaseDAO().insert(mainCase);
				}
				
				if(SessionDAO.getInstance().getCachedBC() != null)
				{
					Criterion mainCrt = MethodApplication.getRelatedCriterion(mainMethod.getID(), methodApplications).get(0); //There can't be more than one criterion related to the same main method in the JSP page
					Criterion originalCrt = SessionDAO.getInstance().getCriterionByID(mainCrt.getID());
					if(originalCrt == null)
					{
						originalCrt = mainCrt;
						daoFactory.getCriterionDAO().insert(originalCrt);
					}
					
					Method originalMainMethod = SessionDAO.getInstance().getMethodByID(mainMethod.getID());
					if(originalMainMethod != null)
					{
						originalMainMethod.setName(mainMethod.getName());
						daoFactory.getMethodDAO().update(originalMainMethod);
					}
					else
					{
						CalculationMethod calMet = new CalculationMethod(mainMethod.getID(),mainMethod.getName());
						calMet.setParameters(mainMethod.getParameters());
						
						daoFactory.getMethodDAO().insert(calMet);
						originalMainMethod = calMet;
					}
					
					updateBCData(daoFactory, mainCrt, originalCrt, originalMainMethod, methodApplications);
					if(derMeasure != null) //Means that the method output was replaced by one of the derived measures...
						updateCalMethod(daoFactory, mainMethod.getID(), derMeasure.getID());
				}
				else
					createNewBC(methodApplications);
			}
			
			daoFactory.commitChanges();
			response.getWriter().write(gson.toJson("SUCCESS"));
		}
		catch (Exception e) 
		{
			daoFactory.rollbackChanges();
			
			String errorType = "ERROR";
			
			if(e.getClass().equals(JsonSyntaxException.class))
				errorType = "JSON_ERROR";
			else if(e.getClass().equals(DAOException.class))
				errorType = "DATABASE_ERROR";
			
			StackTraceElement ste = Thread.currentThread().getStackTrace()[1];
			String location = "Class: " + ste.getClassName() + ", method: " + ste.getMethodName();
			SessionDAO.getInstance().showError(location, e.getMessage(), e);
			
			response.getWriter().write(gson.toJson(errorType));
		}
		finally
		{
			if(lockOwner)
			{
				MySqlDAOFactory.lockConnection.unlock();
				try { MySqlDAOFactory.getInstance().closeConnection(); } 
				catch (DAOException e) { e.printStackTrace();}
			}
		}
	}
	
	protected void removeParameter(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
	{
		Gson gson = CommonUtils.createGsonInstance(response);
		
		try
		{
			UUID calMetID =  gson.fromJson(request.getParameter("calMetID"), UUID.class);
			UUID crtID =  gson.fromJson(request.getParameter("crtID"), UUID.class);
			Type metAppsType = new TypeToken<LinkedSet<MethodApplication>>(){}.getType(); 
			List<MethodApplication> metApps = gson.fromJson(request.getParameter("metApps"), metAppsType);
			
			CalculationMethod calMet = (CalculationMethod)SessionDAO.getInstance().getMethodByID(calMetID);
			if(calMet.getFormula() != null)
			{
				calMet.getFormula().removeParameter(crtID);
				DAOFactory.getDAOFactory(DAOType.MYSQL).getFormulaDAO().deleteParameter(calMet.getFormula().getID(), crtID);
				DAOFactory.getDAOFactory(DAOType.MYSQL).getFormulaDAO().update(calMet.getFormula());
			}
			
			MetAppDAO metAppDAO = DAOFactory.getDAOFactory(DAOType.MYSQL).getMetAppDAO();
			for(MethodApplication metApp : metApps)
			{
				SessionDAO.getInstance().removeComponent(ComponentType.METHOD_APPLICATION, metApp.getID());
				
				if(metApp.getValue() != null && metAppDAO.assertIsOnlyReference(ComponentType.VALUE, metApp.getValue().getID()))
					SessionDAO.getInstance().removeComponent(ComponentType.VALUE, metApp.getValue().getID());
				if(metApp.getMethod() != null && metAppDAO.assertIsOnlyReference(ComponentType.METHOD, metApp.getMethod().getID()))
					SessionDAO.getInstance().removeComponent(ComponentType.METHOD, metApp.getMethod().getID());
			}
			
			if(metAppDAO.assertIsOnlyCrt(crtID, calMetID))
				SessionDAO.getInstance().removeComponent(ComponentType.CRITERION, crtID);
			
			response.getWriter().write(gson.toJson(""));
		}
		catch(Exception e)
		{
			String errorType = "ERROR";
			
			if(e.getClass().equals(DAOException.class))
				errorType = "DATABASE_ERROR";
			else if(e.getClass().equals(JsonSyntaxException.class))
				errorType = "JSON_ERROR";
			
			StackTraceElement ste = Thread.currentThread().getStackTrace()[1];
			String location = "Class: " + ste.getClassName() + ", method: " + ste.getMethodName();
			SessionDAO.getInstance().showError(location, e.getMessage(), e);
			
			response.getWriter().write(gson.toJson(errorType));
		}
	}
	
	/**
	 * This method is responsible for updating all the calculation methods which some of their parameter had changed. In order to do that, the following steps are executed:
	 *   1. Identify the criterion that the value belongs
	 *   2. Identify the method(s) that uses this criterion as a parameter, in order to know that they must me updated.
	 *   3. Update the corresponding values from the calculation methods.
	 *   4. If some calculation method is used as a parameter to another method, this related method has to be updated as well.
	 *   5. Take all the updated values (excluding the repeated ones) 
	 *   6. Send the updated values ID - result value pair to the client side
	 * @param request The request sent by the client side
	 * @param response Sends a JSON collection representing the value ID - result value pair.
	 * @throws ServletException
	 * @throws IOException
	 */
	protected void updateResult(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
	{
		Gson gson = CommonUtils.createGsonInstance(response);
		
		try
		{
			UUID valID =  gson.fromJson(request.getParameter("valID"), UUID.class);
			String stringResult = request.getParameter("stringResult");
			
			List<UpdatedResult> allUpdatedValues = getPendingUpdates(valID, stringResult);
				
			response.getWriter().write(gson.toJson(allUpdatedValues));
		}
		catch (Exception e) 
		{
			String errorType = "ERROR";
			
			if(e.getClass().equals(FormulaException.class))
				errorType = "CALCULATION_ERROR";
			else if(e.getClass().equals(DAOException.class))
				errorType = "DATABASE_ERROR";
			
			StackTraceElement ste = Thread.currentThread().getStackTrace()[1];
			String location = "Class: " + ste.getClassName() + ", method: " + ste.getMethodName();
			SessionDAO.getInstance().showError(location, e.getMessage(), e);
			
			response.getWriter().write(gson.toJson(errorType));
		}
	}
	
	protected void validateChanges(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
	{
		Gson gson = CommonUtils.createGsonInstance(response);
		
		try
		{
			//Type metsAppType = new TypeToken<LinkedSet<MethodApplication>>(){}.getType();  
			//LinkedSet<MethodApplication> methodApplications = gson.fromJson(request.getParameter("methodApplications"), metsAppType);
			
			String fields = "";	
			
			/*for(Criterion crt : MethodApplication.getRelatedCriteria(methodApplications))
				if(crt.getName().trim().equals(""))
					fields += "Criterion name, ";
			
			for(MethodApplication metApp : methodApplications) //Values currently in the JSP page
			{
				Value paramValue = metApp.getValue();
				
				if(paramValue.getUnit() == null || paramValue.getUnit().trim().equals(""))
					fields += metApp.getCriterion().getName() + ": Value's Metric missing, ";
				if(paramValue.getStringResult() == null || paramValue.getStringResult().trim().equals(""))
					fields += metApp.getCriterion().getName() + ": Value's result, ";
			}
			
			if(!fields.equals(""))
				fields = fields.substring(0, fields.length()-2); //Removes the last unnecessary ','*/
			
			response.getWriter().write(gson.toJson(fields));
		}
		catch (Exception e) 
		{
			String errorType = "ERROR";
			
			if(e.getClass().equals(JsonSyntaxException.class))
				errorType = "JSON_ERROR";
			
			StackTraceElement ste = Thread.currentThread().getStackTrace()[1];
			String location = "Class: " + ste.getClassName() + ", method: " + ste.getMethodName();
			SessionDAO.getInstance().showError(location, e.getMessage(), e);
			
			response.getWriter().write(gson.toJson(errorType));
		}
	}
	
	protected void verifyPendingChanges(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
	{
		Gson gson = CommonUtils.createGsonInstance(response);
		boolean hasChange = false;
		
		try
		{
			saveMethodData(request, response);
			/*Criterion mainCrt = gson.fromJson(request.getParameter("parameter"), Criterion.class);
			Criterion originalCrt = SessionDAO.getInstance().getBcCriterionByID(mainCrt.getID());
			
			Type type = new TypeToken<List<GsonMethodApplication>>(){}.getType(); 
			List<GsonMethodApplication> gsonMethodApplications = gson.fromJson(request.getParameter("metApplications"), type);
			
			hasChange = ((originalCrt == null) || (!originalCrt.getName().equalsIgnoreCase(mainCrt.getName())));
			if(!hasChange)
				hasChange = (!originalCrt.getDescription().equalsIgnoreCase(mainCrt.getDescription()));
			
			if(!hasChange)
			{
				MAIN_LOOP : for(Method m : mainCrt.getMethods()) //Methods currently in the JSP page 
					for(Criterion param : m.getParameters()) //Methods parameters currently in the JSP page
					{
						Criterion originalCriterion = SessionDAO.getInstance().getCriterionByID(param.getID(), originalCrt);
						if((originalCriterion == null) || (!originalCriterion.getName().equalsIgnoreCase(param.getName())))
						{
							hasChange = true;
							break;
						}
						
						for(Case paramCase : param.getCases()) //Cases currently in the JSP page
						{
							Case originalCase = SessionDAO.getInstance().getCaseByID(paramCase.getID(), originalCriterion);
							
							if((originalCase == null) || (!originalCase.getName().equalsIgnoreCase(paramCase.getName())))
							{
								hasChange = true;
								break MAIN_LOOP;
							}
							
							UUID paramMethodID = getRelatedMethod(param.getID(), paramCase.getID(), gsonMethodApplications);
							Value paramValue = SessionDAO.getInstance().getMethodByID(paramMethodID, param).getValue(paramCase);
							Value originalValue = SessionDAO.getInstance().getValueByID(paramValue.getID(), originalCriterion, originalCase);
							
							hasChange = ((originalValue == null) || (!originalValue.getUnit().equalsIgnoreCase(paramValue.getUnit())));
							if(!hasChange)
								hasChange = (!originalValue.getStringResult().equalsIgnoreCase(paramValue.getStringResult()));
							
							if(hasChange)
								break MAIN_LOOP;
						}
					}
			}*/
			
			response.getWriter().write(gson.toJson(hasChange));
		}
		catch (Exception e) 
		{
			StackTraceElement ste = Thread.currentThread().getStackTrace()[1];
			String location = "Class: " + ste.getClassName() + ", method: " + ste.getMethodName();
			SessionDAO.getInstance().showError(location, e.getMessage(), e);
			
			response.getWriter().write(gson.toJson("ERROR"));
		}
	}
	
	private List<UpdatedResult> searchForPendingUpdates(UUID valID, String stringResult) throws FormulaException, DAOException
	{
		LinkedSet<UpdatedResult> updatedValues = null;
		
		List<CalculationMethod> calMethods = null;
		for(MethodApplication metApp : SessionDAO.getInstance().getMethodsApplication())
			if(metApp.getValue() != null && metApp.getValue().getID().equals(valID))
			{
				metApp.getValue().setStringResult(stringResult);
				Criterion relatedParameter = metApp.getCriterion();
				calMethods = MethodApplication.getCaculationMethodsByParameter(relatedParameter, SessionDAO.getInstance().getMethodsApplication());
				break;
			}
		
		if(calMethods != null)
		{
			updatedValues = new LinkedSet<UpdatedResult>();
			for(CalculationMethod calMet : calMethods)
			{
				//TODO: Possible improvement -> Currently all the cases are queried, only the related value cases should be queried. 
				List<Case> metCases = SessionDAO.getInstance().getMethodRelatedCases(calMet.getID());
				for(Case caso : metCases)
				{
					Value val = calMet.refreshValue(caso);
					UpdatedResult updatedResult = new UpdatedResult(val.getStringResult(), val.getID());
					updatedValues.add(updatedResult);
				}
			}
		}
		
		return updatedValues;
	}
	
	private LinkedSet<UpdatedResult> getPendingUpdates(UUID valID, String newResult) throws FormulaException, DAOException
	{
		List<UpdatedResult> updatedValues = searchForPendingUpdates(valID, newResult);
		LinkedSet<UpdatedResult> allUpdatedValues = new LinkedSet<UpdatedResult>();
		if(updatedValues != null)
		{
			allUpdatedValues.addCollection(updatedValues);
			
			for(UpdatedResult updatedResult : updatedValues)
			{
				updatedValues = searchForPendingUpdates(updatedResult.getValueID(), updatedResult.getResult());
				
				if(updatedValues != null)
					allUpdatedValues.addCollection(updatedValues);
				else
					break;
			}
		}
		
		return allUpdatedValues;
	}
	
	private void updateCalMethod(DAOFactory daoFactory, UUID calMetID, UUID derMeasureID) throws FormulaException, DAOException
	{
		//TODO: Confirm if the method is working correctly...
		CalculationMethod calMet = (CalculationMethod)SessionDAO.getInstance().getMethodByID(calMetID);
		Criterion derivedMeasure = (Criterion)SessionDAO.getInstance().getCriterionByID(derMeasureID);
		
		Formula calMetFormula = calMet.getFormula();
		CalculationMethod derMet = SessionDAO.getInstance().getCriterionCalculationMethods(derivedMeasure.getID()).get(0);
		
		calMet.setFormula(derMet.getFormula());
		derMet.setFormula(calMetFormula);
		
		Criterion newDerivedMeasure = new Criterion();
		newDerivedMeasure.setName(calMet.getName());
		calMet.removeDerivedMeasure(derivedMeasure);
		
		calMet.setName(derivedMeasure.getName());
		calMet.addDerivedMeasure(newDerivedMeasure);
		
		daoFactory.getMethodDAO().update(calMet);
	}
	
	/*private void createValue(Criterion param, Case paramCase, Value paramValue, UUID paramMethodID)
	{
		Method newMethod = new Method("defualt mehtod", paramMethodID);
		String paramResult = paramValue.getStringResult();
		if(StringUtils.containsLetter(paramResult) || paramResult.trim().equals(""))
		{
			QualitativeValue qV = new QualitativeValue(paramResult);
			qV.setUnit(paramValue.getUnit());
			qV.setReliability(paramValue.getReliability());
			newMethod.setValue(qV);
		}
		else
		{
			QuantitativeValue qV = new QuantitativeValue (Double.parseDouble(paramResult));
			qV.setUnit(paramValue.getUnit());
			qV.setReliability(paramValue.getReliability());
			newMethod.setValue(qV);
		}
		
		//param.addMethod(newMethod);
		SessionDAO.getInstance().addMethodApplication(new MethodApplication(param, paramCase, newMethod, newMethod.getValue(paramCase)));

	}*/
	
	/*private UUID getRelatedMethod(UUID paramID, UUID caseID, List<GsonMethodApplication> gsonMethodApplications)
	{
		UUID retVal = null;
		
		for(GsonMethodApplication gsonMet : gsonMethodApplications)
			if(gsonMet.getCaseID().equals(caseID) && gsonMet.getParamID().equals(paramID))
			{
				retVal = gsonMet.getMethodID();
				break;
			}
		
		return retVal;
	}*/
	
	/*private void insertRefreshMethod(Criterion originalCrt,Case paramCase, Method m, Set<Case> cases, HashMap<Method, Set<Case>> needRefresh) throws DAOException
	{
		Method originalMethod = SessionDAO.getInstance().getMethodByID(m.getID());
		if(needRefresh.containsKey(originalMethod))
			needRefresh.get(originalMethod).add(paramCase);
		else
		{
			cases.add(paramCase);
			needRefresh.put(originalMethod,cases);
		}
	}*/
}